package com.lemon.exam.common.redis.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lemon.exam.common.exception.CustomException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;

import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;

/**
 * 密封类
 * 操作redis后的数据转换及重试机制的基类
 *
 * @author Lemon
 * @since 2025/7/4 17:55
 */
@Slf4j
public sealed class BaseOperations permits StringOperations, HashOperations, SetOperations, ListOperations, ZSetOperations, LockOperations, IdOperations {
    protected final ObjectMapper objectMapper;
    protected final ReactiveRedisTemplate<String, Object> template;

    public BaseOperations(ObjectMapper objectMapper, ReactiveRedisTemplate<String, Object> template) {
        this.objectMapper = objectMapper;
        this.template = template;
    }

    /**
     * 将对象转换为指定类型
     *
     * @param clazz
     * @param <T>
     * @return
     */
    protected <T> Function<Object, T> toGeneric(Class<T> clazz) {
        return value -> {
            if (value == null) return null;
            if (clazz == String.class) return clazz.cast(value.toString());
            if (clazz == Long.class) return clazz.cast(Long.valueOf(value.toString()));
            if (clazz == Integer.class) return clazz.cast(Integer.valueOf(value.toString()));
            if (clazz == Boolean.class) return clazz.cast(Boolean.valueOf(value.toString()));
            try {
                return objectMapper.convertValue(value, clazz);
            } catch (Exception e) {
                throw new CustomException("数据转换失败：%s".formatted(e.getMessage()));
            }
        };
    }

    /**
     * 重试操作
     *
     * @param name
     * @return
     */
    private Retry retry(String name) {
        return Retry.backoff(3, Duration.ofMillis(100))
                .doBeforeRetry(retry -> log.warn("操作Redis正在重试 (第{}次): {}", retry.totalRetries() + 1, name))
                .onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> new CustomException("重试后失败: " + name));
    }

    /**
     * 重试操作
     *
     * @param operation
     * @param operationName
     * @param <T>
     * @return
     */
    protected <T> Mono<T> withRetry(Mono<T> operation, String operationName) {
        Instant start = Instant.now();
        return operation.retryWhen(retry(operationName))
                .doOnSuccess(t -> log.info("操作Redis成功 (耗时{}ms): {}", Duration.between(start, Instant.now()).toMillis(), operationName));
    }

    /**
     * 重试操作
     *
     * @param operation
     * @param operationName
     * @param <T>
     * @return
     */
    protected <T> Flux<T> withRetry(Flux<T> operation, String operationName) {
        Instant start = Instant.now();
        return operation.retryWhen(retry(operationName))
                .doOnComplete(() -> log.info("操作Redis成功 (耗时{}ms): {}", Duration.between(start, Instant.now()).toMillis(), operationName));
    }
}
